home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / mxutil / tspak17 / playwav.asm < prev    next >
Encoding:
Assembly Source File  |  1994-07-13  |  34.9 KB  |  1,735 lines

  1. ; PLAYWAV.ASM
  2. ;
  3. ; Version 1.2 - July 13, 1994.  Modified to improve the chip detection
  4. ; routine.
  5. ;
  6. ; This is a program to play .wav files on the Tandy 1000 SL/TL/RL models
  7. ; (and other Tandy models) with the built-in DAC.  The most common types
  8. ; of .wav are playable:  8- or 16-bit mono or stereo, at sampling rates
  9. ; up to 65535 Hz.
  10. ;
  11. ; Syntax:
  12. ;   PLAYWAV <filename>
  13. ; There are no command-line options, and the program doesn't display anything
  14. ; on the screen unless there's an error.
  15. ;
  16. ; This program is based on information contained in the _Tandy 1000TL
  17. ; Technical Reference Manual_; the TLSLSND package by Bruce M. Terry, Jr.;
  18. ; and the RIFF WAVE file format specification excerpted by Rob Ryan from the
  19. ; official Microsoft "Multimedia Programming Interface and Data Specification,
  20. ; v.1.0."
  21. ;
  22. ; This program is in .exe format due to memory requirements greater than
  23. ; 64k.
  24. ;
  25. ; Order of segments:  code first.
  26. ;
  27. SCODE        SEGMENT
  28. SCODE        ENDS
  29.         ;
  30.         ; Static data.
  31.         ;
  32.         ; Error and debugging messages.
  33.         ;
  34. SDATA        SEGMENT
  35. NODACMSG    DB    "Tandy DAC not detected.",0Dh,0Ah,"$"
  36. NONAMEERR    DB    "Must specify input .wav file.",0Dh,0Ah,"$"
  37. FOPENERR    DB    "Open failed on input file.",0Dh,0Ah,"$"
  38. READERRMSG    DB    "Error reading .wav file.",0Dh,0Ah,"$"
  39. BADWAVMSG    DB    ".wav file invalid or unsupported type.",0Dh,0Ah,"$"
  40. UNSUPPMSG    DB    "Unsupported sample size or number of channels in"
  41.         DB    " .wav file.",0Dh,0Ah,"$"
  42. UNDERFLOWMSG    DB    "Output underflow - unable to maintain sampling rate."
  43.         DB    0Dh,0Ah
  44.         DB    "Copy .wav to hard disk or RAM disk and try again,"
  45.         DB    0Dh,0Ah
  46.         DB    "convert to 8-bit mono, or convert to reduce sampling"
  47.         DB    0Dh,0Ah,"rate.",0Dh,0Ah,"$"
  48.         ;
  49.         ; Default interrupt vectors, for restoring at exit.
  50.         ;
  51. INT1BDEFAULT    DD    0
  52.         ;
  53.         ; Default control-C check flag, for restoring at exit.
  54.         ;
  55. CTRLC        DB    0
  56.         ;
  57.         ; Flag, indicates .wav type.  0 = 8-bit mono PCM (special
  58.         ; case, makes fast code).  1 = 8-bit stereo (requires mixing).  
  59.         ; 2 = 16-bit mono (requires conversion to 8-bit).  3 = 16-bit
  60.         ; stereo (requires mixing and converstion to 8-bit).  4 =
  61.         ; unsupported type.
  62.         ;
  63. WAVTYPE        DW    0
  64. NCHANNELS    DW    0        ; number of channels
  65. SAMPRATE    DW    0        ; sampling rate
  66. DMACLOCK    DD    3579545        ; DMA clock rate, for computing divider
  67. DIVIDER        DW    0        ; DAC divider value to match SAMPRATE
  68. SAMPLESIZE    DW    0        ; bytes per multichannel sample
  69. SAMPLES32K    DW    0        ; number of multichannel samples in 32k
  70. SHIFT        DB    0        ; number of shifts to divide by the
  71.                     ;   number of bytes in a multichannel 
  72.                     ;   sample
  73.         ;
  74.         ; Flag, 1 = format chunk processed.
  75.         ;
  76. FMTDONE        DB    0
  77.         ;
  78.         ; Array of pointers to .wav playing procedures.
  79.         ;
  80. WAVPROCS    DW    OFFSET MONO8
  81.         DW    OFFSET STEREO8
  82.         DW    OFFSET MONO16
  83.         DW    OFFSET STEREO16
  84.         ;
  85.         ; File handle for .wav file.
  86.         ;
  87. HANDLE        DW    0
  88.         ;
  89.         ; Small input buffer and comparison strings for analyzing
  90.         ; the header.
  91.         ;
  92. HEADERBUF    DB    12 DUP (0)
  93. RIFFSTR        DB    "RIFF"        ; RIFF signature
  94. WAVESTR        DB    "WAVE"        ; WAVE signature
  95. FMTSTR        DB    "fmt "        ; format chunk header
  96. DATASTR        DB    "data"        ; data chunk header
  97.         ;
  98.         ; Pointers to current buffers and full/empty flags.
  99.         ;
  100. CURRENTPLAY    DW    0        ; current play buffer
  101. FILLSTATUS    DB    2 DUP (0)    ; 0 = empty; 1 = full
  102. LASTBUFFER    DB    2 DUP (0)    ; 1 = this is the last buffer to play
  103. INBUFFER    DW    0        ; number of bytes in last buffer filled
  104. BUFFERSEGS    DW    2 DUP (0)    ; buffer segments, initialized at
  105.                     ;   runtime, due to assembler bug
  106. DONE        DB    0        ; 1 = all data has been played
  107. SOUNDHALTED    DB    0        ; 1 = sound chip finalized after playing
  108.         ;
  109.         ; Underflow flag.  This flag is set if the output underflows,
  110.         ; i.e., if the processor does not keep up with the output
  111.         ; sampling rate.
  112.         ;
  113. UNDERFLOW    DB    0
  114. SDATA        ENDS
  115.         ;
  116.         ; Then the stack.
  117.         ;
  118. STACK        SEGMENT STACK
  119.         DW    512 DUP (?)
  120. STACK        ENDS
  121.         ;
  122.         ; First DMA buffer.
  123.         ;
  124. BUFFER0        SEGMENT
  125.         DB    32768 DUP (?)
  126. BUFFER0     ENDS
  127.         ;
  128.         ; Second DMA buffer.
  129.         ;
  130. BUFFER1        SEGMENT
  131.         DB    32768 DUP (?)
  132. BUFFER1        ENDS
  133.         ;
  134.         ; File buffer, 128k.  Sufficient to load 32768 16-bit
  135.         ; stereo samples.
  136.         ;
  137. FILEBUFFER    SEGMENT
  138.         DB    32768 DUP (?)
  139. FILEBUFFER    ENDS
  140. FB2        SEGMENT
  141.         DB    32768 DUP (?)
  142. FB2        ENDS
  143. FB3        SEGMENT
  144.         DB    32768 DUP (?)
  145. FB3        ENDS
  146. FB4        SEGMENT
  147.         DB    32768 DUP (?)
  148. FB4        ENDS
  149.  
  150. SCODE        SEGMENT
  151. ;
  152. ; Interrupt handlers.  Default Int 15h vector must be in code segment.
  153. ;
  154. INT15DEFAULT    DD    0
  155. ;
  156. ; Control-break handler.  Does nothing, disabling control-break.
  157. ;
  158. INT1BHDLR    PROC    FAR
  159.         IRET
  160. INT1BHDLR    ENDP
  161.         ;
  162.         ; Control-C handler.  Halts sound output, restores interrupt
  163.         ; vectors, and terminates the program.
  164.         ;
  165. INT23HDLR    PROC    FAR
  166.         MOV    AX,SEG SDATA
  167.         MOV    DS,AX
  168.         CALL    HALTSOUND
  169.         CALL    UNHOOK
  170.         ;
  171.         ; Restore Int 15h vector to default.
  172.         ;
  173.         PUSH    DS
  174.         MOV    AX,2515h
  175.         MOV    DX,WORD PTR CS:INT15DEFAULT
  176.         MOV    DS,WORD PTR CS:INT15DEFAULT+2
  177.         INT    21h
  178.         POP    DS
  179.         ;
  180.         ; Restore control-C check flag.
  181.         ;
  182.         MOV    AX,3301h
  183.         MOV    DL,CTRLC
  184.         INT    21h
  185.         ;
  186.         ; Terminate (no error).
  187.         ;
  188.         MOV    AX,4C00h
  189.         INT    21h
  190. INT23HDLR    ENDP
  191.         ;
  192.         ; Replacement critical error handler.  Fails the system
  193.         ; call (DOS 3.1 and later only).
  194.         ;
  195. INT24HDLR    PROC    FAR
  196.         MOV    AL,3
  197.         IRET
  198. INT24HDLR    ENDP
  199.         ;
  200.         ; First Int 15h handler, for intercepting the BIOS call out
  201.         ; made when sound output finishes.  First, check for the
  202.         ; magic number indicating end of sound output.
  203.         ;
  204. INT15HDLR1    PROC    FAR
  205.         CMP    AX,91FBh
  206.         JE    >L0
  207.         JMP    DWORD PTR CS:INT15DEFAULT
  208.         ;
  209.         ; Finished playing a sound buffer.  
  210.         ;
  211. L0:        PUSH    AX
  212.         PUSH    BX
  213.         PUSH    CX
  214.         PUSH    DX
  215.         PUSH    SI
  216.         PUSH    ES
  217.         PUSH    DS
  218.         ;
  219.         ; DS addresses our data, SI is the last buffer played.
  220.         ;
  221.         MOV    AX,SEG SDATA
  222.         MOV    DS,AX
  223.         MOV    SI,CURRENTPLAY
  224.         ;
  225.         ; Check if this is the last buffer.  If so, set the done
  226.         ; flag and exit.
  227.         ;
  228.         CMP    LASTBUFFER[SI],1
  229.         JNE    >L1
  230.         MOV    DONE,1
  231.         JMP    INT15HDLR1_EXIT
  232.         ;
  233.         ; This is not the last buffer.  Mark this buffer empty and
  234.         ; switch play buffers.
  235.         ;
  236. L1:        MOV    FILLSTATUS[SI],0
  237.         XOR    SI,1
  238.         MOV    CURRENTPLAY,SI
  239.         ;
  240.         ; If the current play buffer is not full (and ready to play),
  241.         ; the processor is not keeping up with the output sampling
  242.         ; rate.  Set the done flag and the underflow flag and exit.
  243.         ;
  244.         CMP    FILLSTATUS[SI],0
  245.         JNE    >L2
  246.         MOV    DONE,1
  247.         MOV    UNDERFLOW,1
  248.         JMP    INT15HDLR1_EXIT
  249.         ;
  250.         ; There is more to play.  Start it playing.
  251.         ;
  252. L2:        SHL    SI,1
  253.         MOV    AX,BUFFERSEGS[SI]
  254.         MOV    ES,AX
  255.         XOR    BX,BX
  256.         MOV    AX,8307h
  257.         MOV    CX,INBUFFER
  258.         MOV    DX,DIVIDER
  259.         INT    1Ah
  260.         ;
  261.         ; Exit.
  262.         ;
  263. INT15HDLR1_EXIT:
  264.         POP    DS
  265.         POP    ES
  266.         POP    SI
  267.         POP    DX
  268.         POP    CX
  269.         POP    BX
  270.         POP    AX
  271.         IRET
  272. INT15HDLR1    ENDP
  273.         ;
  274.         ; Second Int 15h handler.  This handler is used (1) before
  275.         ; the first buffer is played, to make sure the sound chip
  276.         ; is ready; and (2) on termination, when preparing the sound
  277.         ; chip for the next application that uses it.
  278.         ;
  279. INT15HDLR2    PROC    FAR
  280.         CMP    AX,91FBh
  281.         JE    >L0
  282.         JMP    DWORD PTR CS:INT15DEFAULT
  283. L0:        PUSH    AX
  284.         PUSH    DS
  285.         MOV    AX,SEG SDATA
  286.         MOV    DS,AX
  287.         MOV    SOUNDHALTED,1
  288.         POP    DS
  289.         POP    AX
  290.         IRET
  291. INT15HDLR2    ENDP
  292.         
  293. ;
  294. ; Subroutines.
  295. ;
  296. ; Routine to check whether there is BIOS support for Tandy sound functions.  
  297. ; Sets carry if not.  (Modified - July 13, 1994.)
  298. ;
  299. CHKDAC        PROC    NEAR
  300.         PUSH    AX
  301.         PUSH    CX
  302.         ;
  303.         ; Check for PCMCIA Socket Services.
  304.         ;
  305.         MOV    AX,8003h
  306.         XOR    CX,CX
  307.         INT    1Ah
  308.         CMP    CX,5353h
  309.         JE    CHKDAC_NODAC
  310.         ;
  311.         ; Check for Tandy DAC.
  312.         ;
  313.         MOV    AX,8100h
  314.         INT    1Ah
  315.         CMP    AX,8100h
  316.         JE    CHKDAC_NODAC
  317.         ;
  318.         ; DAC found.
  319.         ;
  320.         CLC
  321.         JMP    CHKDAC_END
  322. CHKDAC_NODAC:
  323.         STC
  324. CHKDAC_END:
  325.         POP    CX
  326.         POP    AX
  327.         RET
  328. CHKDAC        ENDP
  329.         ;
  330.         ; Routine to hook interrupts needed, other than Int 15h.
  331.         ;
  332. HOOK        PROC    NEAR
  333.         PUSH    AX
  334.         PUSH    BX
  335.         PUSH    DX
  336.         PUSH    ES
  337.         ;
  338.         ; Save default vectors.
  339.         ;
  340.         MOV    AX,351Bh
  341.         INT    21h
  342.         MOV    WORD PTR INT1BDEFAULT,BX
  343.         MOV    WORD PTR INT1BDEFAULT+2,ES
  344.         ;
  345.         ; Set vectors.
  346.         ;
  347.         PUSH    DS
  348.         MOV    AX,SEG SCODE
  349.         MOV    DS,AX
  350.         MOV    DX,OFFSET INT1BHDLR
  351.         MOV    AX,251Bh
  352.         INT    21h
  353.         MOV    DX,OFFSET INT23HDLR
  354.         MOV    AX,2523h
  355.         INT    21h
  356.         MOV    DX,OFFSET INT24HDLR
  357.         MOV    AX,2524h
  358.         INT    21h
  359.         POP    DS
  360.         POP    ES
  361.         POP    DX
  362.         POP    BX
  363.         POP    AX
  364.         RET
  365. HOOK        ENDP
  366.         ;
  367.         ; Routine to unhook interrupts before termination, other than
  368.         ; Int 15h.
  369.         ;
  370. UNHOOK        PROC    NEAR
  371.         PUSH    AX
  372.         PUSH    DX
  373.         PUSH    DS
  374.         MOV    DX,WORD PTR INT1BDEFAULT
  375.         MOV    DS,WORD PTR INT1BDEFAULT+2
  376.         MOV    AX,251Bh
  377.         INT    21h
  378.         POP    DS
  379.         POP    DX
  380.         POP    AX
  381.         RET
  382. UNHOOK        ENDP
  383.         ;
  384.         ; Routine to start sound output.
  385.         ;
  386. STARTPLAY    PROC    NEAR
  387.         PUSH    AX
  388.         PUSH    BX
  389.         PUSH    CX
  390.         PUSH    DX
  391.         PUSH    ES
  392.         ;
  393.         ; Use the second Int 15h handler now.
  394.         ;
  395.         PUSH    DS
  396.         MOV    DX,OFFSET INT15HDLR2
  397.         MOV    AX,SEG SCODE
  398.         MOV    DS,AX
  399.         MOV    AX,2515h
  400.         INT    21h
  401.         POP    DS
  402.         ;
  403.         ; Prepare the sound chip.  (Mr. Terry's code.)
  404.         ;
  405.         MOV    SOUNDHALTED,0
  406. L0:        MOV    AH,84h
  407.         INT    1Ah
  408. L1:        MOV    AH,81h
  409.         INT    1Ah
  410.         JC    L1
  411.         CMP    SOUNDHALTED,0
  412.         JE    L0
  413.         ;
  414.         ; Switch to the first Int 15h handler.
  415.         ;
  416.         PUSH    DS
  417.         MOV    DX,OFFSET INT15HDLR1
  418.         MOV    AX,SEG SCODE
  419.         MOV    DS,AX
  420.         MOV    AX,2515h
  421.         INT    21h
  422.         POP    DS
  423.         ;
  424.         ; Start playing the first buffer.
  425.         ;
  426.         MOV    AX,SEG BUFFER0
  427.         MOV    ES,AX
  428.         XOR    BX,BX
  429.         MOV    CX,INBUFFER
  430.         MOV    DX,DIVIDER
  431.         MOV    AX,8307h
  432.         INT    1Ah
  433.         POP    ES
  434.         POP    DX
  435.         POP    CX
  436.         POP    BX
  437.         POP    AX
  438.         RET
  439. STARTPLAY    ENDP
  440.         ;
  441.         ; Routine to halt sound output.  Sets up the sound chip for
  442.         ; the "next" application.
  443.         ;
  444. HALTSOUND    PROC    NEAR
  445.         PUSH    AX
  446.         PUSH    DX
  447.         ;
  448.         ; Use the second Int 15h handler now.
  449.         ;
  450.         PUSH    DS
  451.         MOV    DX,OFFSET INT15HDLR2
  452.         MOV    AX,SEG SCODE
  453.         MOV    DS,AX
  454.         MOV    AX,2515h
  455.         INT    21h
  456.         POP    DS
  457.         ;
  458.         ; Finalize the sound chip.  (Mr. Terry's code.)
  459.         ;
  460.         MOV    SOUNDHALTED,0
  461. L0:        MOV    AH,84h
  462.         INT    1Ah
  463. L1:        MOV    AH,81h
  464.         INT    1Ah
  465.         JC    L1
  466.         CMP    SOUNDHALTED,0
  467.         JE    L0
  468.         POP    DX
  469.         POP    AX
  470.         RET
  471. HALTSOUND    ENDP
  472.         ;
  473.         ; Error handling routine.  Display the error message addressed 
  474.         ; by DS:DX, halt sound output, unhook interrupts, and exit the 
  475.         ; program with an errorlevel of 1.  Note that DOS will close
  476.         ; the input file and restore the vectors for Int 23h and Int
  477.         ; 24h automatically.
  478.         ;
  479. ERROR        PROC    NEAR
  480.         MOV    AH,9
  481.         INT    21h
  482.         CALL    HALTSOUND
  483.         CALL    UNHOOK
  484.         ;
  485.         ; Restore Int 15h vector to default.
  486.         ;
  487.         PUSH    DS
  488.         MOV    AX,2515h
  489.         MOV    DX,WORD PTR CS:INT15DEFAULT
  490.         MOV    DS,WORD PTR CS:INT15DEFAULT+2
  491.         INT    21h
  492.         POP    DS
  493.         ;
  494.         ; Restore control-C check flag.
  495.         ;
  496.         MOV    AX,3301h
  497.         MOV    DL,CTRLC
  498.         INT    21h
  499.         ;
  500.         ; Terminate with error.
  501.         ;
  502.         MOV    AX,4C01h
  503.         INT    21h
  504. ERROR        ENDP
  505.         ;
  506.         ; Get input filename from the command line, returning its
  507.         ; address in ES:DX.  Assumes that ES addresses the PSP
  508.         ; on entry.
  509.         ;
  510. GETFILENAME    PROC    NEAR
  511.         PUSH    AX
  512.         PUSH    CX
  513.         PUSH    DI
  514.         MOV    DI,80h
  515.         MOV    CL,ES:[DI]
  516.         XOR    CH,CH
  517.         JCXZ    GETFILENAME_NONAME
  518.         INC    DI
  519.         MOV    AL,' '
  520.         REPE    SCASB
  521.         JE    GETFILENAME_NONAME
  522.         DEC    DI
  523.         INC    CX
  524.         MOV    DX,DI
  525.         REPNE    SCASB
  526.         JNE    >L0
  527.         DEC    DI
  528. L0:        MOV    BYTE PTR ES:[DI],0
  529.         POP    DI
  530.         POP    CX
  531.         POP    AX
  532.         RET
  533. GETFILENAME_NONAME:
  534.         MOV    DX,OFFSET NONAMEERR
  535.         CALL    ERROR        
  536. GETFILENAME    ENDP
  537.         ;
  538.         ; Routine to open the input file.  ASCIIZ name passed in
  539.         ; ES:DX.
  540.         ;
  541. FOPEN        PROC    NEAR
  542.         PUSH    AX
  543.         PUSH    DS
  544.         MOV    AX,ES
  545.         MOV    DS,AX
  546.         MOV    AX,3D20h        ; open read-only, deny writes
  547.         INT    21h
  548.         POP    DS
  549.         JNC    >L0
  550.         MOV    DX,OFFSET FOPENERR
  551.         CALL    ERROR
  552. L0:        MOV    HANDLE,AX
  553.         POP    AX
  554.         RET
  555. FOPEN        ENDP
  556.         ;
  557.         ; Subroutine to read a small number of bytes (specified in
  558.         ; CX) from the file into HEADERBUF.  Returns number of bytes
  559.         ; in AX, pointer to HEADERBUF in DX.
  560.         ;
  561. READSMALL    PROC    NEAR
  562.         PUSH    BX
  563.         MOV    AH,3Fh
  564.         MOV    BX,HANDLE
  565.         MOV    DX,OFFSET HEADERBUF
  566.         INT    21h
  567.         JC    READSMALL_READERR
  568.         CMP    AX,CX
  569.         JNE    READSMALL_INVALID
  570.         POP    BX
  571.         RET
  572.         ;
  573.         ; Error reading .wav file header.
  574.         ;
  575. READSMALL_READERR:
  576.         MOV    DX,OFFSET READERRMSG
  577.         CALL    ERROR
  578.         ;
  579.         ; Invalid .wav header, or unsupported .wav type.
  580.         ;
  581. READSMALL_INVALID:
  582.         MOV    DX,OFFSET BADWAVMSG
  583.         CALL    ERROR
  584. READSMALL    ENDP
  585.         ;
  586.         ; Subroutine for GETWAVHEADER.  Reads 4 bytes from the input
  587.         ; file and checks whether the string read is "fmt ", "data",
  588.         ; another ASCII    string, or not ASCII.  Returns AL = 0 if
  589.         ; "fmt ", AL = 1 if "data", AL = -1 if another ASCII string.
  590.         ; Halts the program if not ASCII.  Assumes ES addresses data 
  591.         ; segment.  Returns pointer to HEADERBUF in DX.
  592.         ;
  593. GETCHUNK    PROC    NEAR
  594.         PUSH    BX
  595.         PUSH    CX
  596.         PUSH    SI
  597.         PUSH    DI
  598.         ;
  599.         ; Read 4 bytes from the file.
  600.         ;
  601.         MOV    CX,4
  602.         CALL    READSMALL
  603.         ;
  604.         ; Check if format chunk.
  605.         ;
  606.         MOV    SI,DX
  607.         MOV    DI,OFFSET FMTSTR
  608.         REPE    CMPSB
  609.         JNE    GETCHUNK_CHKDATA
  610.         MOV    AL,0
  611.         JMP    GETCHUNK_EXIT
  612.         ;
  613.         ; Check if data chunk.
  614.         ;
  615. GETCHUNK_CHKDATA:
  616.         MOV    SI,DX
  617.         MOV    DI,OFFSET DATASTR
  618.         MOV    CX,4
  619.         REPE    CMPSB
  620.         JNE    GETCHUNK_CHKASCII
  621.         MOV    AL,1
  622.         JMP    GETCHUNK_EXIT
  623.         ;
  624.         ; Check if a valid chunk header of another type.
  625.         ;
  626. GETCHUNK_CHKASCII:
  627.         MOV    SI,DX
  628.         MOV    CX,4
  629. GETCHUNK_ASCLOOP:
  630.         LODSB
  631.         CMP    AL,20h
  632.         JB    GETCHUNK_INVALID
  633.         CMP    AL,7Eh
  634.         JA    GETCHUNK_INVALID
  635.         LOOP    GETCHUNK_ASCLOOP
  636.         MOV    AL,-1
  637.         ;
  638.         ; Read the chunk length field and exit.
  639.         ;
  640. GETCHUNK_EXIT:    PUSH    AX
  641.         MOV    CX,4
  642.         CALL    READSMALL
  643.         POP    AX
  644.         POP    DI
  645.         POP    SI
  646.         POP    CX
  647.         POP    BX
  648.         RET
  649.         ;
  650.         ; Invalid .wav header, or unsupported .wav type.
  651.         ;
  652. GETCHUNK_INVALID:
  653.         MOV    DX,OFFSET BADWAVMSG
  654.         CALL    ERROR
  655. GETCHUNK    ENDP
  656.         ;
  657.         ; Subroutine for GETWAVHEADER.  This routine reads the
  658.         ; format chunk from the file and records the information
  659.         ; there.  Assumes DS:DX addresses chunk length.
  660.         ;
  661. DOFORMAT    PROC    NEAR
  662.         PUSH    AX
  663.         PUSH    BX
  664.         PUSH    CX
  665.         PUSH    DX
  666.         PUSH    SI
  667.         ;
  668.         ; Check chunk length (must be 16).
  669.         ;
  670.         MOV    SI,DX
  671.         LODSW
  672.         CMP    AX,16
  673.         JNE    DOFORMAT_INVALID
  674.         MOV    CX,AX
  675.         LODSW
  676.         OR    AX,AX
  677.         JNZ    DOFORMAT_INVALID
  678.         ;
  679.         ; Read in format chunk.
  680.         ;
  681.         CALL    READSMALL
  682.         ;
  683.         ; Verify format tag.
  684.         ;
  685.         MOV    SI,DX
  686.         LODSW
  687.         CMP    AX,1
  688.         JNE    DOFORMAT_INVALID
  689.         ;
  690.         ; Verify number of channels and save.
  691.         ;
  692.         LODSW
  693.         CMP    AX,1
  694.         JE    DOFORMAT_CHOK
  695.         CMP    AX,2
  696.         JE    DOFORMAT_CHOK
  697.         MOV    WAVTYPE,4
  698.         JMP    DOFORMAT_EXIT
  699. DOFORMAT_CHOK:    MOV    NCHANNELS,AX
  700.         ;
  701.         ; Get sampling rate.
  702.         ;
  703.         LODSW
  704.         MOV    SAMPRATE,AX
  705.         LODSW
  706.         OR    AX,AX            ; no rate above 65535/sec.
  707.         JNZ    DOFORMAT_INVALID
  708.         ;
  709.         ; Get bits per sample.
  710.         ;
  711.         ADD    SI,6            ; skip bytes/sec, block align
  712.         LODSW
  713.         OR    AX,AX            ; 0-bit samples invalid
  714.         JZ    DOFORMAT_INVALID
  715.         CMP    AX,16
  716.         JNA    DOFORMAT_BITSOK
  717.         MOV    WAVTYPE,4        ; can't play >16 bit samples
  718.         JMP    DOFORMAT_EXIT
  719.         ;
  720.         ; Determine .wav type (0, 1, 2 or 3) and store.
  721.         ;
  722. DOFORMAT_BITSOK:
  723.         DEC    AX
  724.         SHR    AX,1
  725.         SHR    AX,1
  726.         AND    AL,2
  727.         MOV    BX,NCHANNELS
  728.         DEC    BX
  729.         ADD    AX,BX
  730.         MOV    WAVTYPE,AX
  731.         ;
  732.         ; Compute divider value for DAC to match sampling rate.
  733.         ;
  734. DOFORMAT_DODIV:    MOV    BX,SAMPRATE
  735.         CMP    BX,875
  736.         JB    DOFORMAT_INVALID
  737.         ;
  738.         ; Compute divider.  Round to nearest.
  739.         ;
  740.         MOV    AX,WORD PTR DMACLOCK
  741.         MOV    DX,WORD PTR DMACLOCK+2
  742.         DIV    BX
  743.         SHR    BX,1
  744.         CMP    BX,DX
  745.         ADC    AX,0
  746.         MOV    DIVIDER,AX
  747.         ;
  748.         ; Exit.
  749.         ;
  750. DOFORMAT_EXIT:    POP    SI
  751.         POP    DX
  752.         POP    CX
  753.         POP    BX
  754.         POP    AX
  755.         RET
  756.         ;
  757.         ; Invalid .wav header, or unsupported .wav type.
  758.         ;
  759. DOFORMAT_INVALID:
  760.         MOV    DX,OFFSET BADWAVMSG
  761.         CALL    ERROR
  762. DOFORMAT    ENDP
  763.         ;
  764.         ; Subroutine for GETWAVHEADER.  This routine skips over an
  765.         ; unknown chunk, updating the file pointer.  Assumes DS:DX
  766.         ; addresses the chunk length.
  767.         ;
  768. SKIPCHUNK    PROC    NEAR
  769.         PUSH    AX
  770.         PUSH    BX
  771.         PUSH    CX
  772.         PUSH    DX
  773.         PUSH    SI
  774.         MOV    SI,DX
  775.         LODSW
  776.         MOV    DX,AX
  777.         LODSW
  778.         MOV    CX,AX
  779.         MOV    AX,4201h
  780.         MOV    BX,HANDLE
  781.         INT    21h
  782.         POP    SI
  783.         POP    DX
  784.         POP    CX
  785.         POP    BX
  786.         POP    AX
  787.         RET
  788. SKIPCHUNK    ENDP
  789.         ;
  790.         ; Routine to read the .wav header from the file and compute
  791.         ; needed parameters, such as the DAC divider value and sample
  792.         ; size, from it.  This version skips unknown chunks.
  793.         ;
  794. GETWAVHEADER    PROC    NEAR
  795.         PUSH    AX
  796.         PUSH    CX
  797.         PUSH    DX
  798.         PUSH    SI
  799.         PUSH    DI
  800.         PUSH    ES
  801.         ;
  802.         ; ES addresses data segment.
  803.         ;
  804.         MOV    AX,DS
  805.         MOV    ES,AX
  806.         ;
  807.         ; Read RIFF and WAVE headers from file.
  808.         ;
  809.         MOV    CX,12
  810.         CALL    READSMALL
  811.         ;
  812.         ; Verify "RIFF".
  813.         ;
  814.         MOV    SI,DX
  815.         MOV    DI,OFFSET RIFFSTR
  816.         MOV    CX,4
  817.         REPE    CMPSB
  818.         JNE    GETWAVHEADER_INVALID
  819.         ;
  820.         ; Verify "WAVE".
  821.         ;
  822.         ADD    SI,4            ; skip length field
  823.         MOV    DI,OFFSET WAVESTR
  824.         MOV    CX,4
  825.         REPE    CMPSB
  826.         JNE    GETWAVHEADER_INVALID
  827.         ;
  828.         ; Loop over chunks until data chunk found.
  829.         ;
  830. GETWAVHEADER_LOOP:
  831.         CALL    GETCHUNK
  832.         CMP    AL,0            ; format chunk?
  833.         JNE    >L0
  834.         CMP    FMTDONE,1        ; error if > 1 format chunk
  835.         JE    GETWAVHEADER_INVALID
  836.         CALL    DOFORMAT
  837.         MOV    FMTDONE,1        ; mark format done
  838.         JMP    GETWAVHEADER_LOOP
  839. L0:        CMP    AL,1            ; data chunk?
  840.         JNE    >L1
  841.         CMP    FMTDONE,0        ; error if format chunk does
  842.         JE    GETWAVHEADER_INVALID    ;   not precede data chunk
  843.         JMP    GETWAVHEADER_LOOPEND
  844. L1:        CALL    SKIPCHUNK        ; unknown chunk, skip
  845.         JMP    GETWAVHEADER_LOOP
  846.         ;
  847.         ; Data chunk found; skip length field.
  848.         ;
  849. GETWAVHEADER_LOOPEND:
  850.         MOV    CX,4
  851.         CALL    READSMALL
  852.         POP    ES
  853.         POP    DI
  854.         POP    SI
  855.         POP    DX
  856.         POP    CX
  857.         POP    AX
  858.         RET
  859.         ;
  860.         ; Invalid .wav header, or unsupported .wav type.
  861.         ;
  862. GETWAVHEADER_INVALID:
  863.         MOV    DX,OFFSET BADWAVMSG
  864.         CALL    ERROR
  865. GETWAVHEADER    ENDP
  866.         ;
  867.         ; Routine to play an 8-bit mono .wav.  These .wav's require
  868.         ; no mixing or conversion, so the playing routine can be
  869.         ; optimized.
  870.         ;
  871. MONO8        PROC    NEAR
  872.         PUSH    AX
  873.         PUSH    BX
  874.         PUSH    CX
  875.         PUSH    DX
  876.         PUSH    SI
  877.         ;
  878.         ; Fill first DMA buffer.
  879.         ;
  880.         PUSH    DS
  881.         MOV    BX,HANDLE
  882.         MOV    AX,SEG BUFFER0
  883.         MOV    DS,AX
  884.         MOV    AH,3Fh
  885.         MOV    CX,32768
  886.         XOR    DX,DX
  887.         INT    21h
  888.         POP    DS
  889.         JNC    MONO8_CHKEMPTY
  890.         JMP    MONO8_FILEERR
  891.         ;
  892.         ; If zero bytes were read, the file contains no samples (an
  893.         ; unusual case, to say the least).  Exit immediately.
  894.         ;
  895. MONO8_CHKEMPTY:    OR    AX,AX
  896.         JNZ    MONO8_CHKSHORT
  897.         JMP    MONO8_EXIT
  898.         ;
  899.         ; If fewer than 32768 (but more than zero) bytes were read,
  900.         ; mark this buffer last, record the number of bytes in it,
  901.         ; mark it full, start playing it, and go wait for it to be 
  902.         ; played.  This is for a short file with fewer than 32768
  903.         ; samples.
  904.         ;
  905. MONO8_CHKSHORT:    CMP    AX,32768
  906.         JE    MONO8_NOTSHORT
  907.         MOV    LASTBUFFER,1
  908.         MOV    INBUFFER,AX
  909.         MOV    FILLSTATUS,1
  910.         CALL    STARTPLAY
  911.         JMP    MONO8_WAITEND
  912.         ;
  913.         ; Not a short file.  Record the number of bytes in this
  914.         ; buffer, mark it full, and start playing it.
  915.         ;
  916. MONO8_NOTSHORT:    MOV    INBUFFER,AX
  917.         MOV    FILLSTATUS,1
  918.         CALL    STARTPLAY
  919.         ;
  920.         ; Loop, filling buffers when they become empty.  SI is the
  921.         ; current fill buffer.
  922.         ;
  923.         MOV    SI,1
  924.         ;
  925.         ; First, wait until the current fill buffer becomes empty.
  926.         ; Halt program with error if underflow is detected.
  927.         ;
  928. MONO8_LOOP:    CMP    UNDERFLOW,1
  929.         JE    MONO8_UNDERFLOW
  930.         CMP    FILLSTATUS[SI],0
  931.         JNE    MONO8_LOOP
  932.         ;
  933.         ; Fill buffer.
  934.         ;
  935.         PUSH    DS
  936.         MOV    BX,HANDLE
  937.         SHL    SI,1
  938.         MOV    AX,BUFFERSEGS[SI]
  939.         SHR    SI,1
  940.         MOV    DS,AX
  941.         MOV    AH,3Fh
  942.         MOV    CX,32768
  943.         XOR    DX,DX
  944.         INT    21h
  945.         POP    DS
  946.         JC    MONO8_FILEERR
  947.         ;
  948.         ; If zero bytes were read, the previous buffer was the last
  949.         ; buffer.  Mark it so and exit the loop.
  950.         ;
  951.         OR    AX,AX
  952.         JNZ    MONO8_NONZERO
  953.         XOR    SI,1
  954.         MOV    LASTBUFFER[SI],1
  955.         JMP    MONO8_WAITEND
  956.         ;
  957.         ; If fewer than 32768 (but more than zero) bytes were read,
  958.         ; mark this buffer last, record the number of bytes in it,
  959.         ; mark it full, and exit the loop.
  960.         ;
  961. MONO8_NONZERO:    CMP    AX,32768
  962.         JE    MONO8_NOTEND
  963.         MOV    LASTBUFFER[SI],1
  964.         MOV    INBUFFER,AX
  965.         MOV    FILLSTATUS[SI],1
  966.         JMP    MONO8_WAITEND
  967.         ;
  968.         ; This is not the last buffer.  Record the number of bytes
  969.         ; in it, mark it full, switch buffers, and go again.
  970.         ;
  971. MONO8_NOTEND:    MOV    INBUFFER,AX
  972.         MOV    FILLSTATUS[SI],1
  973.         XOR    SI,1
  974.         JMP    MONO8_LOOP
  975.         ;
  976.         ; All data has been read.  Wait until it's through playing,
  977.         ; then set up the sound chip for the next application that
  978.         ; uses it.
  979.         ;
  980. MONO8_WAITEND:    CMP    DONE,1
  981.         JNE    MONO8_WAITEND
  982.         CALL    HALTSOUND
  983.         ;
  984.         ; Exit.
  985.         ;
  986. MONO8_EXIT:    POP    SI
  987.         POP    DX
  988.         POP    CX
  989.         POP    BX
  990.         POP    AX
  991.         RET
  992.         ;
  993.         ; Error reading input .wav file.
  994.         ;
  995. MONO8_FILEERR:    MOV    DX,OFFSET READERRMSG
  996.         CALL    ERROR
  997.         ;
  998.         ; Output underflow.
  999.         ;
  1000. MONO8_UNDERFLOW:
  1001.         MOV    DX,OFFSET UNDERFLOWMSG
  1002.         CALL    ERROR
  1003. MONO8        ENDP
  1004.         ;
  1005.         ; Subroutine for MONO16 and STEREO16 (which call it READ16)
  1006.         ; and STEREO8 (which calls it READ8).  This routine reads 
  1007.         ; 32768 8-bit stereo or 16-bit samples into the input buffer, 
  1008.         ; if possible.  Exits program on file error.  Returns AX = 
  1009.         ; number of samples read.
  1010.         ;
  1011. READ16        PROC    NEAR
  1012. READ8        EQU    READ16
  1013.         PUSH    BX
  1014.         PUSH    CX
  1015.         PUSH    DX
  1016.         PUSH    SI
  1017.         PUSH    DI
  1018.         MOV    CX,SAMPLESIZE        ; CX is read loop counter
  1019.         XOR    SI,SI            ; SI counts samples read
  1020.         MOV    BX,HANDLE        ; BX is file handle
  1021.         MOV    DI,SEG FILEBUFFER    ; DI is current segment
  1022.         XOR    DX,DX            ; DX is offset (always zero)
  1023. READ16_LOOP:    PUSH    CX            ; save loop counter
  1024.         PUSH    DS            ; save DS
  1025.         MOV    AH,3Fh            ; read into buffer
  1026.         MOV    DS,DI
  1027.         MOV    CX,32768
  1028.         INT    21h
  1029.         POP    DS            ; restore DS
  1030.         JC    READ16_ERROR        ; exit if file error
  1031.         CMP    AX,CX            ; if < 32768 bytes read ...
  1032.         JB    READ16_EOF        ; ... compute samples read
  1033.         ADD    SI,SAMPLES32K        ; else add constant
  1034.         ADD    DI,800h            ; go to next 32k segment
  1035.         POP    CX            ; restore loop counter
  1036.         LOOP    READ16_LOOP
  1037.         ;
  1038.         ; End of loop.  SI contains samples read.  Return in AX.
  1039.         ;
  1040. READ16_ENDLOOP:    MOV    AX,SI
  1041.         POP    DI
  1042.         POP    SI
  1043.         POP    DX
  1044.         POP    CX
  1045.         POP    BX
  1046.         RET
  1047.         ;
  1048.         ; End-of-file encountered.  SI contains number of samples
  1049.         ; read, excluding this last read.  AX contains number of bytes
  1050.         ; read this time, less than 32k.
  1051.         ;
  1052. READ16_EOF:    POP    CX            ; clear stack
  1053.         MOV    CL,SHIFT
  1054.         SHR    AX,CL
  1055.         ADD    SI,AX            ; add last few samples to count
  1056.         JMP    READ16_ENDLOOP        ; "rejoin the class"
  1057.         ;
  1058.         ; File error encountered.
  1059.         ;
  1060. READ16_ERROR:    MOV    DX,OFFSET READERRMSG
  1061.         CALL    ERROR
  1062. READ16        ENDP
  1063.         ;
  1064.         ; Routine to play an 8-bit stereo .wav.  8-bit .wav's use 
  1065.         ; unsigned samples.
  1066.         ;
  1067. STEREO8        PROC    NEAR
  1068.         PUSH    AX
  1069.         PUSH    BX
  1070.         PUSH    CX
  1071.         PUSH    SI
  1072.         PUSH    DI
  1073.         PUSH    ES
  1074.         ;
  1075.         ; Set sample size, samples per 32k, and number of shifts to
  1076.         ; divide by the number of bytes in a sample.
  1077.         ;
  1078.         MOV    SAMPLESIZE,2
  1079.         MOV    SAMPLES32K,16384
  1080.         MOV    SHIFT,1
  1081.         ;
  1082.         ; Fill input buffer.
  1083.         ;
  1084.         CALL    READ8
  1085.         ;
  1086.         ; If zero bytes were read, the file contains no samples (an
  1087.         ; unusual case, to say the least).  Exit immediately.
  1088.         ;
  1089.         OR    AX,AX
  1090.         JNZ    STEREO8_NOTEMPTY
  1091.         JMP    STEREO8_EXIT
  1092.         ;
  1093.         ; Save the number of samples.
  1094.         ;
  1095. STEREO8_NOTEMPTY:
  1096.         MOV    INBUFFER,AX
  1097.         ;
  1098.         ; Fill the first DMA buffer.
  1099.         ;
  1100.         PUSH    AX
  1101.         PUSH    DS
  1102.         MOV    CX,AX            ; CX counts samples
  1103.         MOV    ES,BUFFERSEGS        ; ES:DI addresses DMA buffer
  1104.         XOR    DI,DI
  1105.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1106.         MOV    DS,AX
  1107.         XOR    SI,SI
  1108.         ;
  1109.         ; Loop over stereo 8-bit samples.
  1110.         ;
  1111. STEREO8_LP1:    LODSW                ; get 2 channels
  1112.         MOV    BL,AH
  1113.         XOR    AH,AH
  1114.         ADD    AL,BL
  1115.         ADC    AH,0
  1116.         ;
  1117.         ; Divide by number of channels (2) and store in DMA buffer.
  1118.         ;
  1119.         SHR    AX,1
  1120.         STOSB
  1121.         LOOP    STEREO8_LP1        ; go again if more
  1122.         POP    DS
  1123.         POP    AX
  1124.         ;
  1125.         ; If fewer than 32768 (but more than zero) bytes were read,
  1126.         ; mark this buffer last, mark it full, start playing sound,
  1127.         ; and go wait for it to finish.  This is for a short file 
  1128.         ; with fewer than 32768 samples.
  1129.         ;
  1130.         CMP    AX,32768
  1131.         JE    STEREO8_NOTSHORT
  1132.         MOV    LASTBUFFER,1
  1133.         MOV    FILLSTATUS,1
  1134.         CALL    STARTPLAY
  1135.         JMP    STEREO8_WAITEND
  1136.         ;
  1137.         ; Start playing.
  1138.         ;
  1139. STEREO8_NOTSHORT:
  1140.         MOV    FILLSTATUS,1
  1141.         CALL    STARTPLAY
  1142.         ;
  1143.         ; Loop, filling buffers when they become empty.  SI is the 
  1144.         ; current fill buffer.
  1145.         ;
  1146.         MOV    SI,1
  1147.         ;
  1148.         ; First, wait until the current fill buffer becomes empty.
  1149.         ; Halt program with error if underflow is detected.
  1150.         ;
  1151. STEREO8_LOOP:    CMP    UNDERFLOW,1
  1152.         JNE    STEREO8_CHKFILLSTAT
  1153.         MOV    DX,OFFSET UNDERFLOWMSG
  1154.         CALL    ERROR
  1155. STEREO8_CHKFILLSTAT:
  1156.         CMP    FILLSTATUS[SI],0
  1157.         JNE    STEREO8_LOOP
  1158.         ;
  1159.         ; Read in 32k more samples (if possible).
  1160.         ;
  1161.         CALL    READ8
  1162.         ;
  1163.         ; If zero bytes were read, the previous buffer was the last
  1164.         ; buffer.  Mark it so and exit the loop.
  1165.         ;
  1166.         OR    AX,AX
  1167.         JNZ    STEREO8_NONZERO
  1168.         XOR    SI,1
  1169.         MOV    LASTBUFFER[SI],1
  1170.         JMP    STEREO8_WAITEND
  1171.         ;
  1172.         ; Save number of samples.
  1173.         ;
  1174. STEREO8_NONZERO:
  1175.         MOV    INBUFFER,AX
  1176.         ;
  1177.         ; Fill the next DMA buffer.
  1178.         ;
  1179.         PUSH    AX
  1180.         PUSH    SI
  1181.         PUSH    DS
  1182.         MOV    CX,AX            ; CX counts samples
  1183.         SHL    SI,1            ; ES:DI addresses DMA buffer
  1184.         MOV    ES,BUFFERSEGS[SI]
  1185.         XOR    DI,DI
  1186.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1187.         MOV    DS,AX
  1188.         XOR    SI,SI
  1189.         ;
  1190.         ; Loop over stereo 8-bit samples.
  1191.         ;
  1192. STEREO8_LP2:    LODSW                ; get 2 channels
  1193.         MOV    BL,AH            ; add the two
  1194.         XOR    AH,AH
  1195.         ADD    AL,BL
  1196.         ADC    AH,0
  1197.         ;
  1198.         ; Divide by number of channels (2) and store in DMA buffer.
  1199.         ;
  1200.         SHR    AX,1
  1201.         STOSB
  1202.         LOOP    STEREO8_LP2        ; go again if more
  1203.         POP    DS
  1204.         POP    SI
  1205.         POP    AX
  1206.         ;
  1207.         ; If fewer than 32768 (but more than zero) bytes were read,
  1208.         ; mark this buffer last, mark it full, and exit the loop.
  1209.         ;
  1210.         CMP    AX,32768
  1211.         JE    STEREO8_NOTEND
  1212.         MOV    LASTBUFFER[SI],1
  1213.         MOV    FILLSTATUS[SI],1
  1214.         JMP    STEREO8_WAITEND
  1215.         ;
  1216.         ; This is not the last buffer.  Mark it full, switch buffers, 
  1217.         ; and go again.
  1218.         ;
  1219. STEREO8_NOTEND:    MOV    FILLSTATUS[SI],1
  1220.         XOR    SI,1
  1221.         JMP    STEREO8_LOOP
  1222.         ;
  1223.         ; All data has been read.  Wait until it's through playing,
  1224.         ; then set up the sound chip for the next application that
  1225.         ; uses it.
  1226.         ;
  1227. STEREO8_WAITEND:
  1228.         CMP    DONE,1
  1229.         JNE    STEREO8_WAITEND
  1230.         CALL    HALTSOUND
  1231.         ;
  1232.         ; Exit.
  1233.         ;
  1234. STEREO8_EXIT:    POP    ES
  1235.         POP    DI
  1236.         POP    SI
  1237.         POP    CX
  1238.         POP    BX
  1239.         POP    AX
  1240.         RET
  1241. STEREO8        ENDP
  1242.         ;
  1243.         ; Routine to play 16-bit mono .wav's.  These use signed
  1244.         ; samples.
  1245.         ;
  1246. MONO16        PROC    NEAR
  1247.         PUSH    AX
  1248.         PUSH    CX
  1249.         PUSH    SI
  1250.         PUSH    DI
  1251.         PUSH    ES
  1252.         ;
  1253.         ; Set sample size, samples per 32k, and number of shifts to
  1254.         ; divide by the number of bytes in a sample.
  1255.         ;
  1256.         MOV    SAMPLESIZE,2
  1257.         MOV    SAMPLES32K,16384
  1258.         MOV    SHIFT,1
  1259.         ;
  1260.         ; Fill input buffer.
  1261.         ;
  1262.         CALL    READ16
  1263.         ;
  1264.         ; If zero bytes were read, the file contains no samples (an
  1265.         ; unusual case, to say the least).  Exit immediately.
  1266.         ;
  1267.         OR    AX,AX
  1268.         JNZ    MONO16_NOTEMPTY
  1269.         JMP    MONO16_EXIT
  1270.         ;
  1271.         ; Save the number of samples.
  1272.         ;
  1273. MONO16_NOTEMPTY:
  1274.         MOV    INBUFFER,AX
  1275.         ;
  1276.         ; Fill the first DMA buffer.
  1277.         ;
  1278.         PUSH    AX
  1279.         PUSH    DS
  1280.         MOV    CX,AX            ; CX counts samples
  1281.         MOV    ES,BUFFERSEGS        ; ES:DI addresses DMA buffer
  1282.         XOR    DI,DI
  1283.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1284.         MOV    DS,AX
  1285.         XOR    SI,SI
  1286.         ;
  1287.         ; Loop over mono 16-bit samples.
  1288.         ;
  1289. MONO16_LP1:    LODSW                ; get a sample
  1290.         MOV    AL,AH            ; convert to 8-bit unsigned
  1291.         ADD    AL,128
  1292.         STOSB                ; store in DMA buffer
  1293.         LOOP    MONO16_LP1        ; go again if more
  1294.         POP    DS
  1295.         POP    AX
  1296.         ;
  1297.         ; If fewer than 32768 (but more than zero) bytes were read,
  1298.         ; mark this buffer last.  This is for a short file with fewer 
  1299.         ; than 32768 samples.
  1300.         ;
  1301.         CMP    AX,32768
  1302.         JE    MONO16_NOTSHORT
  1303.         MOV    LASTBUFFER,1
  1304.         MOV    FILLSTATUS,1
  1305.         CALL    STARTPLAY
  1306.         JMP    MONO16_WAITEND
  1307.         ;
  1308.         ; Start playing.
  1309.         ;
  1310. MONO16_NOTSHORT:
  1311.         MOV    FILLSTATUS,1
  1312.         CALL    STARTPLAY
  1313.         ;
  1314.         ; Loop, filling buffers when they become empty.  SI is the 
  1315.         ; current fill buffer.
  1316.         ;
  1317.         MOV    SI,1
  1318.         ;
  1319.         ; First, wait until the current fill buffer becomes empty.
  1320.         ; Halt program with error if underflow is detected.
  1321.         ;
  1322. MONO16_LOOP:    CMP    UNDERFLOW,1
  1323.         JE    MONO16_UNDERFLOW
  1324.         CMP    FILLSTATUS[SI],0
  1325.         JNE    MONO16_LOOP
  1326.         ;
  1327.         ; Read in 32k more samples (if possible).
  1328.         ;
  1329.         CALL    READ16
  1330.         ;
  1331.         ; If zero bytes were read, the previous buffer was the last
  1332.         ; buffer.  Mark it so and exit the loop.
  1333.         ;
  1334.         OR    AX,AX
  1335.         JNZ    MONO16_NONZERO
  1336.         XOR    SI,1
  1337.         MOV    LASTBUFFER[SI],1
  1338.         JMP    MONO16_WAITEND
  1339.         ;
  1340.         ; Save number of samples.
  1341.         ;
  1342. MONO16_NONZERO:    MOV    INBUFFER,AX
  1343.         ;
  1344.         ; Fill the next DMA buffer.
  1345.         ;
  1346.         PUSH    AX
  1347.         PUSH    SI
  1348.         PUSH    DS
  1349.         MOV    CX,AX            ; CX counts samples
  1350.         SHL    SI,1            ; ES:DI addresses DMA buffer
  1351.         MOV    ES,BUFFERSEGS[SI]
  1352.         XOR    DI,DI
  1353.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1354.         MOV    DS,AX
  1355.         XOR    SI,SI
  1356.         ;
  1357.         ; Loop over mono 16-bit samples.
  1358.         ;
  1359. MONO16_LP2:    LODSW                ; get a sample
  1360.         MOV    AL,AH
  1361.         ADD    AL,128            ; convert to unsigned
  1362.         STOSB                ; place in DMA buffer
  1363.         LOOP    MONO16_LP2        ; go again if more
  1364.         POP    DS
  1365.         POP    SI
  1366.         POP    AX
  1367.         ;
  1368.         ; If fewer than 32768 (but more than zero) bytes were read,
  1369.         ; mark this buffer last, mark it full, and exit the loop.
  1370.         ;
  1371.         CMP    AX,32768
  1372.         JE    MONO16_NOTEND
  1373.         MOV    LASTBUFFER[SI],1
  1374.         MOV    FILLSTATUS[SI],1
  1375.         JMP    MONO16_WAITEND
  1376.         ;
  1377.         ; This is not the last buffer.  Mark it full, switch buffers, 
  1378.         ; and go again.
  1379.         ;
  1380. MONO16_NOTEND:    MOV    FILLSTATUS[SI],1
  1381.         XOR    SI,1
  1382.         JMP    MONO16_LOOP
  1383.         ;
  1384.         ; All data has been read.  Wait until it's through playing,
  1385.         ; then set up the sound chip for the next application that
  1386.         ; uses it.
  1387.         ;
  1388. MONO16_WAITEND:    CMP    DONE,1
  1389.         JNE    MONO16_WAITEND
  1390.         CALL    HALTSOUND
  1391.         ;
  1392.         ; Exit.
  1393.         ;
  1394. MONO16_EXIT:    POP    ES
  1395.         POP    DI
  1396.         POP    SI
  1397.         POP    CX
  1398.         POP    AX
  1399.         RET
  1400.         ;
  1401.         ; Output underflow.
  1402.         ;
  1403. MONO16_UNDERFLOW:
  1404.         MOV    DX,OFFSET UNDERFLOWMSG
  1405.         CALL    ERROR
  1406. MONO16        ENDP
  1407.         ;
  1408.         ; Routine to play 16-bit stereo .wav's.  These use signed 
  1409.         ; samples and require mixing.
  1410.         ;
  1411. STEREO16    PROC    NEAR
  1412.         PUSH    AX
  1413.         PUSH    BX
  1414.         PUSH    CX
  1415.         PUSH    SI
  1416.         PUSH    DI
  1417.         PUSH    BP
  1418.         PUSH    ES
  1419.         ;
  1420.         ; Set sample size, samples per 32k, and number of shifts to
  1421.         ; divide by the number of bytes in a sample.
  1422.         ;
  1423.         MOV    SAMPLESIZE,4
  1424.         MOV    SAMPLES32K,8192
  1425.         MOV    SHIFT,2
  1426.         ;
  1427.         ; Fill input buffer.
  1428.         ;
  1429.         CALL    READ16
  1430.         ;
  1431.         ; If zero bytes were read, the file contains no samples (an
  1432.         ; unusual case, to say the least).  Exit immediately.
  1433.         ;
  1434.         OR    AX,AX
  1435.         JNZ    STEREO16_NOTEMPTY
  1436.         JMP    STEREO16_EXIT
  1437.         ;
  1438.         ; Save the number of samples.
  1439.         ;
  1440. STEREO16_NOTEMPTY:
  1441.         MOV    INBUFFER,AX
  1442.         ;
  1443.         ; Fill the first DMA buffer.
  1444.         ;
  1445.         PUSH    AX
  1446.         PUSH    DS
  1447.         MOV    CX,AX            ; CX counts samples
  1448.         MOV    ES,BUFFERSEGS        ; ES:DI addresses DMA buffer
  1449.         XOR    DI,DI
  1450.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1451.         MOV    DS,AX
  1452.         XOR    SI,SI
  1453.         ;
  1454.         ; Set CX to a maximum of 16k, BP to the remainder of what
  1455.         ; CX was.
  1456.         ;
  1457.         XOR    BP,BP
  1458.         CMP    CX,16384
  1459.         JBE    STEREO16_LP1
  1460.         MOV    BP,CX
  1461.         MOV    CX,16384
  1462.         SUB    BP,CX
  1463.         ;
  1464.         ; Loop over stereo 16-bit samples.
  1465.         ;
  1466. STEREO16_LP1:    LODSW                ; get first channel
  1467.         MOV    AL,AH
  1468.         CBW
  1469.         MOV    BX,AX            ; save in BX
  1470.         LODSW                ; get second channel
  1471.         MOV    AL,AH
  1472.         CBW
  1473.         ADD    AX,BX            ; add into AX
  1474.         ;
  1475.         ; Divide by number of channels (2), convert to unsigned, and
  1476.         ; store in DMA buffer.
  1477.         ;
  1478.         SAR    AX,1
  1479.         ADD    AX,128
  1480.         STOSB
  1481.         LOOP    STEREO16_LP1        ; go again if more
  1482.         MOV    CX,BP
  1483.         JCXZ    STEREO16_LP1END
  1484.         XOR    BP,BP
  1485.         MOV    AX,DS
  1486.         ADD    AH,10h
  1487.         MOV    DS,AX
  1488.         JMP    STEREO16_LP1
  1489. STEREO16_LP1END:
  1490.         POP    DS
  1491.         POP    AX
  1492.         ;
  1493.         ; If fewer than 32768 (but more than zero) bytes were read,
  1494.         ; mark this buffer last.  This is for a short file with fewer 
  1495.         ; than 32768 samples.
  1496.         ;
  1497.         CMP    AX,32768
  1498.         JE    STEREO16_NOTSHORT
  1499.         MOV    LASTBUFFER,1
  1500.         MOV    FILLSTATUS,1
  1501.         CALL    STARTPLAY
  1502.         JMP    STEREO16_WAITEND
  1503.         ;
  1504.         ; Start playing.
  1505.         ;
  1506. STEREO16_NOTSHORT:
  1507.         MOV    FILLSTATUS,1
  1508.         CALL    STARTPLAY
  1509.         ;
  1510.         ; Loop, filling buffers when they become empty.  SI is the 
  1511.         ; current fill buffer.
  1512.         ;
  1513.         MOV    SI,1
  1514.         ;
  1515.         ; First, wait until the current fill buffer becomes empty.
  1516.         ; Halt program with error if underflow is detected.
  1517.         ;
  1518. STEREO16_LOOP:    CMP    UNDERFLOW,1
  1519.         JNE    STEREO16_CHKFILLSTAT
  1520.         MOV    DX,OFFSET UNDERFLOWMSG
  1521.         CALL    ERROR
  1522. STEREO16_CHKFILLSTAT:
  1523.         CMP    FILLSTATUS[SI],0
  1524.         JNE    STEREO16_LOOP
  1525.         ;
  1526.         ; Read in 32k more samples (if possible).
  1527.         ;
  1528.         CALL    READ16
  1529.         ;
  1530.         ; If zero bytes were read, the previous buffer was the last
  1531.         ; buffer.  Mark it so and exit the loop.
  1532.         ;
  1533.         OR    AX,AX
  1534.         JNZ    STEREO16_NONZERO
  1535.         XOR    SI,1
  1536.         MOV    LASTBUFFER[SI],1
  1537.         JMP    STEREO16_WAITEND
  1538.         ;
  1539.         ; Save number of samples.
  1540.         ;
  1541. STEREO16_NONZERO:
  1542.         MOV    INBUFFER,AX
  1543.         ;
  1544.         ; Fill the next DMA buffer.
  1545.         ;
  1546.         PUSH    AX
  1547.         PUSH    SI
  1548.         PUSH    DS
  1549.         MOV    CX,AX            ; CX counts samples
  1550.         SHL    SI,1            ; ES:DI addresses DMA buffer
  1551.         MOV    ES,BUFFERSEGS[SI]
  1552.         XOR    DI,DI
  1553.         MOV    AX,SEG FILEBUFFER    ; DS:SI addresses sample data
  1554.         MOV    DS,AX
  1555.         XOR    SI,SI
  1556.         ;
  1557.         ; Set CX to a maximum of 16k, BP to the remainder of what
  1558.         ; CX was.
  1559.         ;
  1560.         XOR    BP,BP
  1561.         CMP    CX,16384
  1562.         JBE    STEREO16_LP2
  1563.         MOV    BP,CX
  1564.         MOV    CX,16384
  1565.         SUB    BP,CX
  1566.         ;
  1567.         ; Loop over stereo 16-bit samples.
  1568.         ;
  1569. STEREO16_LP2:    LODSW                ; get first channel
  1570.         MOV    AL,AH
  1571.         CBW
  1572.         MOV    BX,AX            ; save in BX
  1573.         LODSW                ; get second channel
  1574.         MOV    AL,AH
  1575.         CBW
  1576.         ADD    AX,BX            ; add into AX
  1577.         ;
  1578.         ; Divide by number of channels (2), convert to unsigned, and
  1579.         ; store in DMA buffer.
  1580.         ;
  1581.         SAR    AX,1
  1582.         ADD    AX,128
  1583.         STOSB
  1584.         LOOP    STEREO16_LP2        ; go again if more
  1585.         MOV    CX,BP
  1586.         JCXZ    STEREO16_LP2END
  1587.         XOR    BP,BP
  1588.         MOV    AX,DS
  1589.         ADD    AH,10h
  1590.         MOV    DS,AX
  1591.         JMP    STEREO16_LP2
  1592. STEREO16_LP2END:
  1593.         POP    DS
  1594.         POP    SI
  1595.         POP    AX
  1596.         ;
  1597.         ; If fewer than 32768 (but more than zero) bytes were read,
  1598.         ; mark this buffer last, mark it full, and exit the loop.
  1599.         ;
  1600.         CMP    AX,32768
  1601.         JE    STEREO16_NOTEND
  1602.         MOV    LASTBUFFER[SI],1
  1603.         MOV    FILLSTATUS[SI],1
  1604.         JMP    STEREO16_WAITEND
  1605.         ;
  1606.         ; This is not the last buffer.  Mark it full, switch buffers, 
  1607.         ; and go again.
  1608.         ;
  1609. STEREO16_NOTEND:
  1610.         MOV    FILLSTATUS[SI],1
  1611.         XOR    SI,1
  1612.         JMP    STEREO16_LOOP
  1613.         ;
  1614.         ; All data has been read.  Wait until it's through playing,
  1615.         ; then set up the sound chip for the next application that
  1616.         ; uses it.
  1617.         ;
  1618. STEREO16_WAITEND:
  1619.         CMP    DONE,1
  1620.         JNE    STEREO16_WAITEND
  1621.         CALL    HALTSOUND
  1622.         ;
  1623.         ; Exit.
  1624.         ;
  1625. STEREO16_EXIT:    POP    ES
  1626.         POP    BP
  1627.         POP    DI
  1628.         POP    SI
  1629.         POP    CX
  1630.         POP    BX
  1631.         POP    AX
  1632.         RET
  1633. STEREO16    ENDP
  1634.  
  1635. ;
  1636. ; Main program.
  1637. ;
  1638. MAIN        PROC    FAR
  1639.         ;
  1640.         ; DS addresses data segment (always; if any routine changes
  1641.         ; DS, it must restore it before returning).
  1642.         ;
  1643.         MOV    AX,SEG SDATA
  1644.         MOV    DS,AX
  1645.         ;
  1646.         ; Initialize BUFFERSEGS.
  1647.         ;
  1648.         MOV    BUFFERSEGS,SEG BUFFER0
  1649.         MOV    BUFFERSEGS+2,SEG BUFFER1
  1650.         ;
  1651.         ; Direction set to increment (always; if any routine changes
  1652.         ; DF, it must restore it before returning).
  1653.         ;
  1654.         CLD
  1655.         ;
  1656.         ; Check whether the needed sound circuitry is present.
  1657.         ;
  1658.         CALL    CHKDAC
  1659.         JNC    HOOKVECS
  1660.         MOV    DX,OFFSET NODACMSG
  1661.         MOV    AH,9
  1662.         INT    21h
  1663.         JMP    TERMINATE
  1664.         ;
  1665.         ; Hook needed interrupt vectors, other than Int 15h.
  1666.         ;
  1667. HOOKVECS:    CALL    HOOK
  1668.         ;
  1669.         ; Save default Int 15h vector in code segment.
  1670.         ;
  1671.         PUSH    ES        ; save PSP for GETFILENAME
  1672.         MOV    AX,3515h
  1673.         INT    21h
  1674.         MOV    WORD PTR CS:INT15DEFAULT,BX
  1675.         MOV    WORD PTR CS:INT15DEFAULT+2,ES
  1676.         POP    ES        ; restore PSP
  1677.         ;
  1678.         ; Save the control-C check flag status, then turn it on so
  1679.         ; that the user can interrupt playing with <cntrl>-C.
  1680.         ;
  1681.         MOV    AX,3300h
  1682.         INT    21h
  1683.         MOV    CTRLC,DL
  1684.         MOV    AX,3301h
  1685.         MOV    DL,1
  1686.         INT    21h
  1687.         ;
  1688.         ; Open the input file.
  1689.         ;
  1690.         CALL    GETFILENAME
  1691.         CALL    FOPEN
  1692.         ;
  1693.         ; Get and process the .wav header.
  1694.         ;
  1695.         CALL    GETWAVHEADER
  1696.         ;
  1697.         ; Play the .wav.
  1698.         ;
  1699.         MOV    BX,WAVTYPE
  1700.         CMP    BX,4
  1701.         JAE    BADTYPE
  1702.         SHL    BX,1
  1703.         CALL    WAVPROCS[BX]
  1704.         JMP    PLAYDONE
  1705. BADTYPE:    MOV    DX,OFFSET UNSUPPMSG
  1706.         MOV    AH,9        ; .wav has unsupported sample size or
  1707.         INT    21h        ;   number of channels
  1708.         ;
  1709.         ; Unhook interrupts, other than Int 15h.
  1710.         ;
  1711. PLAYDONE:    CALL    UNHOOK
  1712.         ;
  1713.         ; Restore Int 15h vector to default.
  1714.         ;
  1715.         PUSH    DS
  1716.         MOV    AX,2515h
  1717.         MOV    DX,WORD PTR CS:INT15DEFAULT
  1718.         MOV    DS,WORD PTR CS:INT15DEFAULT+2
  1719.         INT    21h
  1720.         POP    DS
  1721.         ;
  1722.         ; Restore control-C check flag.
  1723.         ;
  1724.         MOV    AX,3301h
  1725.         MOV    DL,CTRLC
  1726.         INT    21h
  1727.         ;
  1728.         ; Terminate.
  1729.         ;
  1730. TERMINATE:    MOV    AX,4C00h
  1731.         INT    21h
  1732. MAIN        ENDP
  1733. SCODE        ENDS
  1734.         END    MAIN
  1735.